/**
* AUTHOR: MARCUS BELCHER
* VER: 1.2
* DATE: 7/9/2011
* FILE: Main.cpp
* Program start, takes in an input through the command line
* if the .exe is run it will then ask for a input
* via the given input this program will sort through the data and 
* output related statistics.
* Map class is used to store input data
* DataSystem is a class which manages the in/outputs of the program
*/

#include <time.h>
#include <ctime>
#include "main.h"
#include "DataSystem.h"
#include "IslandGlobals.h"
#include "Map.h"

DataSystem* d_System = NULL;
Map* Land = NULL;
bool ProcessInput(string filename);
void SaveFile(const char* Buffer);
/*
Test results
Map: map1_6islands.txt
1 deep node search gives correct results

map2_6islands.txt
2 deep node search gives correct results

map3_5islands.txt
1 deep node search gives correct results
*/

int main (int argc, char *argv[]){
	cout << LINE << endl << TITLE << endl << LINE << endl;

	//Init classes
	int returnVal = 0;
	d_System = new DataSystem();
	if((NULL == d_System) || (!d_System->Initialized()))returnVal = DataSystemError;
	Land = new Map(WIDTH, HEIGHT);
	if(NULL == Land || !Land->Initialized())returnVal = MapError;

	string filename;
	 clock_t programStart = clock();
	if(returnVal == 0){
		if(argc > 1){
			filename = argv[1];
			if(argc > 2) stringstream(argv[2]) >> neighbourFringeIter;
			if(!ProcessInput(argv[1]))returnVal = FileNameError;
		} else {
			cout << "Please enter file name smaller than " << MAX_FILENAME_SIZE << " characters in length" << endl;
			char Buffer[MAX_FILENAME_SIZE];
			cin.getline(Buffer,MAX_FILENAME_SIZE);
			int iter = 1;
			cout << "Would you like to search 1 Neighbours deep or 2?" << endl;
			cin >> iter;
			if(iter >= 1 && iter <= 2)neighbourFringeIter = iter;
			filename = Buffer;
			if(!ProcessInput(filename))returnVal = FileNameError;
		}
	} 
	 clock_t programEnd = clock();
	double programTime = double( programEnd - programStart ) / CLOCKS_PER_SEC;
	//Program output, switch statement to control output and program states
	switch(returnVal){
		case DataSystemError: 
			cout << DataSystemError << endl;
			break;
		case FileNameError: 
			cout << FileName_ERR << endl;
			break;
		case MapError: 
			cout << Map_ERR << endl;
			break;
		case 0:
			cout << "Number of detected landmasses: "	<< Land->GetNumberOfIslands()	<< " from file: " << filename << endl;
			cout << "Time Taken to process: " << programTime << " MSeconds" << endl;
			char Key;
			cout << "Press 'P' to print the map\n'E' to Exit\nOr to save the output 'S filename.txt'" << endl;;
			string sBuffer;
			cin >> sBuffer;
			if(sBuffer.c_str()[0] == 'P')cout << Land->toString();
			if(sBuffer.c_str()[0] == 'E')break;
			if(sBuffer.c_str()[0] == 'S'){
				cin >> sBuffer;
				SaveFile(sBuffer.c_str());
			}
			cout << "Press any key to Exit" <<  endl;
			cin >> Key;
			break;
	}

	DELETE(Land);
	DELETE(d_System);
	cout << EXIT << endl;
	return 0;
}

void SaveFile(const char* Buffer){
	stringstream ss;
	string fileName;
	ss << Buffer;
	//ss >> fileName;
	ss >> fileName;
	cout << "Saving to: " << fileName << endl;
	d_System->clearOutBuffer();//check
	d_System->writeFile(fileName, Land->toString());
}

inline char totalFeatures(char &c){
	if(c == TREE)		amountOfTrees++;
	if(c == MOUNTAIN)	amountOfMountains++;
	if(c == BUILDING)	amountOfBuildings++;
	return c;
}
inline int checkNeighbours(int x, int y){
	//This method searches the node at x,y neighbours. Depending on Map initionalisation
	//the node can see a certain level of neighbours, recursion would be best here.

	int lowestValue = GRID_MAX_VAL;

	if(neighbourFringeIter == 1){
		for(unsigned int i = 0; i < Land->getPointNeighbours(x,y).size();i++){	//iterate neighbours

			int nLandID = Land->getPointNeighbours(x,y).at(i)->islandID;		//get landid
			if(lowestValue > nLandID && nLandID!= GRID_WATER && nLandID != GRID_UNSORTED){	//if nlandid is older(lower) and not water
				lowestValue = nLandID;											//set lowest value landid
			}
		}
	} 

	if(neighbourFringeIter == 2){
		for(unsigned int i = 0; i < Land->getPointNeighbours(x,y).size();i++){					//search neighbours

			int index = Land->getPointNeighbours(x,y).at(i)->indexID;							//getneighbour index
			for(unsigned int j = 0; j < Land->getPointNeighbours(index).size();j++){			//search neighbours neighbours

				int nLandID = Land->getPointNeighbours(index).at(j)->islandID;				
				if(lowestValue > nLandID && nLandID!= GRID_WATER && nLandID != GRID_UNSORTED){	
					lowestValue = nLandID;														
				}
			}
		}
	}
	return lowestValue;
}

bool ProcessInput(string filename){
	cout << "Processing file: " << filename<< endl;
	if(!d_System->readFile(filename))return false;//File read error

	//Copy map to grid structure and delete unneeded memory
	stringstream map(d_System->getInputBuffer()->str());
	d_System->clearInBuffer();

	for(int y = 0; y < HEIGHT;y++){	
		string line;
		map >> line;
		for(int x = 0; x < WIDTH;x++){
			char c = line.c_str()[x];
			Land->element(x,y).isLand = Land->isLand(c);
			Land->element(x,y).feature = totalFeatures(c);
		}
	}

	Land->LinkNeighbours();//searches for and links neighbours

	for(int y = 0; y < HEIGHT;y++){											//row iteration
		for(int x = 0; x < WIDTH;x++){
			if(Land->element(x,y).isLand){									//is it land?
				int nval = checkNeighbours(x,y);							//check neighbours, returns - ifland - an id
				if(nval != GRID_MAX_VAL)Land->element(x,y).islandID = nval;	//if neighbours arnt water give id
				else Land->element(x,y).islandID = ++amountOfIslands;		//else its a new island!
			}
		}
	}

	//PROBLEM 1: This may produce a large amount of islands which are actually one landmass
	//this is due to my neighbour search is only 1 deep, thus cant see far ahead and islands
	//which are mainly surrounded my water or not close to the mainland are at risk!
	//if each node can see their nieghbours and their nieghbours nieghbours then it would 
	//reduce the problem, however would require more loops and iteractions + storage

	//PROBLEM 2: Row Bias, before everything was iterated via Y,X. This allowed a nice
	//nieghbour search however had a risk of not assigning certain land points the correct
	//id, this is due to the points at risk - stated before - were now out of view and cant be
	//seen and assigned properly. this problem occours in areas where limited node connectivity is present

	//My Solution Part 1: Search through the islands and check neighbour values again.
	//if neighbours are less change value to theirs!. This reduces initial miscalculated landmasses

	for(int y = 0; y < HEIGHT;y++){											//row iteration
		for(int x = 0; x < WIDTH;x++){
			if(Land->element(x,y).isLand){									//is it land?
				int nval = checkNeighbours(x,y);
				if(nval != GRID_WATER && nval <= Land->element(x,y).islandID){
					Land->element(x,y).islandID = nval;
				}
			}
		}
	}

	//My Solution Part 2: Searching is also now done by reverse column to avoid previous bias
	//With a reverse column/row search previous uncaught areas are now assigned appropriate values.
	for(int x = WIDTH-1; x > 0;x--){
		for(int y = HEIGHT-1; y > 0;y--){
			if(Land->element(x,y).isLand){
				int nval = checkNeighbours(x,y);
				if(nval != GRID_WATER && nval <= Land->element(x,y).islandID){
					Land->element(x,y).islandID = nval;
				}
			}
		}
	}

	//After reverse do normal search again to catch harder miscalculated values
	//after last search this should find all last hidden values as 3 iterations either way
	//should be enough! However It isnt. The second map named "map2_6islands.txt" had a problem 
	//with the thin line of land between the two larger bodies. the last iteration below solved
	//this problem. (However only when 2 search nodes deep!)
	for(int y = 0; y < HEIGHT;y++){
		for(int x = 0; x < WIDTH;x++){
			if(Land->element(x,y).isLand){
				int nval = checkNeighbours(x,y);
				if(nval != GRID_WATER && nval <= Land->element(x,y).islandID){
					Land->element(x,y).islandID = nval;
				}
			}
		}
	}

	//One last search to find missing values, this time row/column but in revser order
	for(int y = HEIGHT-1; y > 0;y--){
		for(int x = WIDTH-1; x > 0;x--){
			if(Land->element(x,y).isLand){
				int nval = checkNeighbours(x,y);
				if(nval != GRID_WATER && nval <= Land->element(x,y).islandID){
					Land->element(x,y).islandID = nval;
				}
			}
		}
	}

	//Briefly described above this implementation suffers when there are thin strips of land
	//To solve this i altered the "checkNeighbours" method to allow a deeper search. this allows
	//the nodes to "see" their neighbours neighbours thus give correct land id. However this dramatically
	//increases the search area and slows the program down.
	if(DEBUG_PRINT){
		cout << "DEBUG Print: start" << endl;
		Land->PrintGrid();
		cout << "DEBUG Print: end" << endl;
	}
	return true;
}